package com.violet.myorm.core.source;

import com.violet.myorm.annotations.*;
import com.violet.myorm.core.foreign.Foreign;
import com.violet.myorm.core.foreign.ManyForeign;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class Reflect {

    private static final ConcurrentHashMap<Class<?>, String> tableNameCache;
    private static final ConcurrentHashMap<Class<?>, Constructor<?>> constructorCache;

    // 正常字段名称 (非关联字段)
    private static final ConcurrentHashMap<Class<?>, List<Field>> normFieldsCache;

    // 关联字段
    private static final ConcurrentHashMap<Class<?>, List<Field>> foreignFieldsCache;

    // 所有字段缓存
    private static final ConcurrentHashMap<Class<?>, List<Field>> allFieldsCache;

    // 关联字段的构造器
    private static final Constructor<ManyForeign> manyForeignConstructor;
    private static final Constructor<Foreign> foreignConstructor;

    static {
        allFieldsCache = new ConcurrentHashMap<>();
        tableNameCache = new ConcurrentHashMap<>();
        constructorCache = new ConcurrentHashMap<>();
        foreignFieldsCache = new ConcurrentHashMap<>();
        normFieldsCache = new ConcurrentHashMap<>();

        try {
            manyForeignConstructor = ManyForeign.class.getDeclaredConstructor(Class.class, String.class, Object.class);
            manyForeignConstructor.setAccessible(true);

            foreignConstructor = Foreign.class.getDeclaredConstructor(Class.class, String.class, Object.class, boolean.class);
            foreignConstructor.setAccessible(true);
        }
        catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    public static Field getPrimaryKey(Class<?> clazz) {
        for (Field field : getNormFields(clazz)) {
            if (field.isAnnotationPresent(PrimaryKey.class)) return field;
            else if (field.getName().equals("id")) return field;
        }
        throw new RuntimeException("未找到主键字段: " + clazz.getName());
    }

    public static List<Field> getNormFields(Class<?> clazz) {
        return normFieldsCache.computeIfAbsent(clazz, k -> {
            List<Field> fields = getAllFields(k);
            List<Field> normFields = new ArrayList<>(fields.size());
            for (Field field : fields) {
                if (field.isAnnotationPresent(ManyKey.class) || field.isAnnotationPresent(ForeignKey.class)) continue;
                else normFields.add(field);
            }
            return normFields;
        });
    }

    public static List<Field> getForeignFields(Class<?> clazz) {
        return foreignFieldsCache.computeIfAbsent(clazz, k -> {
            List<Field> fields = getAllFields(k);
            List<Field> foreignFields = new ArrayList<>(fields.size());
            for (Field field : fields) {
                if (field.isAnnotationPresent(ManyKey.class) || field.isAnnotationPresent(ForeignKey.class)) foreignFields.add(field);
            }
            return foreignFields;
        });
    }

    public static List<Field> getAllFields(Class<?> clazz) {
        return allFieldsCache.computeIfAbsent(clazz, k -> {
            List<Field> fieldsCache = new ArrayList<>();
            Field[] fields = k.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                fieldsCache.add(field);
            }
            return fieldsCache;
        });
    }

    public static Field getFieldByName(Class<?> aClass, String name) {
        for (Field field : getAllFields(aClass)) {
            if (getFieldName(field).equals(name)) return field;
        }
        throw new RuntimeException("未找到字段: " + name);
    }

    public static String getNormName(String fieldName) { return fieldName.replaceAll("[A-Z]", "_$0").toLowerCase(); }

    public static String getFieldName(Field field) {
        return field.getName().replaceAll("[A-Z]", "_$0").toLowerCase();
    }

    public static String getTableName(Class<?> clazz) {
        TableName annotation = clazz.getAnnotation(TableName.class);
        if (annotation != null) return annotation.value();
        // 大写字母前面拼接下划线
        return tableNameCache.computeIfAbsent(clazz, k ->
            clazz.getSimpleName()
                .replaceAll("[A-Z]", "_$0")
                .toLowerCase()
                .substring(1)
        );
    }

    public static Constructor<?> getConstructor(Class<?> clazz) {
        return constructorCache.computeIfAbsent(clazz, k -> {
            try {
                Constructor<?> declaredConstructor = k.getDeclaredConstructor();
                declaredConstructor.setAccessible(true);
                return declaredConstructor;
            }
            catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        });
    }

    public static Class<?> getForeignGenericType(Field field) {
        if (field.getGenericType() instanceof ParameterizedType parameter) {
            String typeName = parameter.getActualTypeArguments()[0].getTypeName();
            try {
                return ClassLoader.getSystemClassLoader().loadClass(typeName);
            }
            catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
        throw new RuntimeException("未找到泛型类型");
    }

    public static <E> List<E> mapper(Class<E> eClass, ResultSet resultSet, List<Field> fields) {
        try {
            List<E> result = new ArrayList<>();
            Constructor<E> constructor = (Constructor<E>) getConstructor(eClass);
            while (resultSet.next()) {
                E e = constructor.newInstance();
                // 普通字段的封装
                for (Field field : fields) {
                    String fieldName = getFieldName(field);
                    Object value = resultSet.getObject(fieldName);
                    field.set(e, value);
                }
                // 关联字段的封装
                for (Field field : getForeignFields(eClass)) {
                    if (field.getType().equals(ManyForeign.class)) {
                        setManyForeign(field, e);
                    }
                    else if (field.getType().equals(Foreign.class)) {
                        setForeign(field, e);
                    }
                }
                result.add(e);
            }
            return result;
        }
        catch (SQLException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
            throw new RuntimeException(e);
        }
    }

    private static void setManyForeign(Field field, Object e) {
        try {
            ManyKey manyKey = field.getAnnotation(ManyKey.class);
            if (manyKey == null) throw new RuntimeException("未找到 @ManyKey 注解");

            Field keyField = getFieldByName(e.getClass(), Reflect.getNormName(manyKey.field()));
            Class<?> genericType = getForeignGenericType(field);
            field.set(e, manyForeignConstructor.newInstance(
                    genericType,
                    getNormName(manyKey.foreign()),
                    keyField.get(e))
            );
        } catch (InvocationTargetException | InstantiationException | IllegalAccessException ex) {
            throw new RuntimeException(ex);
        }
    }

    private static void setForeign(Field field, Object e) {
        try {
            ForeignKey foreignKey = field.getAnnotation(ForeignKey.class);
            if (foreignKey == null) throw new RuntimeException("未找到 @ForeignKey 注解");

            Field keyField = getFieldByName(e.getClass(), Reflect.getNormName(foreignKey.field()));
            Class<?> genericType = getForeignGenericType(field);
            field.set(e, foreignConstructor.newInstance(
                    genericType,
                    getNormName(foreignKey.foreign()),
                    keyField.get(e),
                    foreignKey.lazy())
            );
        }
        catch (InvocationTargetException | IllegalAccessException | InstantiationException ex) {
            throw new RuntimeException(ex);
        }
    }

}
